package com.gitee.andy.ioc;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ApplicationContext {

	private static final Logger log = LoggerFactory.getLogger(ApplicationContext.class);

	/**
	 * 包含name2BeanCaches values
	 */
	private static ConcurrentHashMap<Class<?>, Object> clazz2BeanCaches = new ConcurrentHashMap<>();

	private static ConcurrentHashMap<String, Object> name2BeanCaches = new ConcurrentHashMap<>();

	public static void registClazzes(Set<Class<?>> clazzes) {
		clazzes.stream().filter(clazz -> {
			return clazz.isAnnotationPresent(Bean.class);
		}).forEach(clazz -> registClazz(clazz, null));
		injectClazzes();
	}

	private static void registClazz(Class<?> clazz, Object object) {
		if (object == null) {
			if ((clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()) || clazz.isAnnotation()
					|| clazz.isAnonymousClass() || clazz.isEnum())) {
				return;
			} else {
				try {
					object = clazz.newInstance();
				} catch (InstantiationException | IllegalAccessException e) {
					log.error("", e);
				}
			}
		}
		Object oldVal;
		Bean annotation = clazz.getAnnotation(Bean.class);
		boolean dependName = annotation != null && !Strings.isEmpty(annotation.name());
		if (dependName) {
			oldVal = name2BeanCaches.put(annotation.name(), object);
			if (oldVal != null) {
				log.error("repeated annotation.name: {}, newVal:{}, oldVal:{}", annotation.name(),
						object.getClass().getName(), oldVal.getClass().getName());
				throw new RuntimeException("repeated annotation.name");
			}
		}

		oldVal = clazz2BeanCaches.put(clazz, object);
		if (oldVal != null) {
			log.error("repeated clazz {}, newVal:{}, oldVal:{}", clazz.getName(), object.getClass().getName(),
					oldVal.getClass().getName());
			throw new RuntimeException("must have annotation.name");
		}

		if (dependName) {
			return;
		}

		Class<?> clazz0 = clazz.getSuperclass();
		if (clazz0 != null && clazz0 != Object.class) {
			registClazz(clazz0, object);
		}

		for (Class<?> clazzInf : clazz.getInterfaces()) {
			registClazz(clazzInf, object);
		}
	}

	public static <T> T fetchBean(Class<T> clazz) {
		return (T) clazz2BeanCaches.get(clazz);
	}

	public static Object fetchBean(String beanName) {
		return name2BeanCaches.get(beanName);
	}

	private static void injectClazzes() {
		clazz2BeanCaches.values().forEach(obj -> {
			Class<?> clazz = obj.getClass();
			while (clazz != Object.class) {
				injectClazz(obj, clazz);
				clazz = clazz.getSuperclass();
			}
		});
	}

	private static void injectClazz(Object obj, Class<?> clazz) {
		Field[] declaredFields = clazz.getDeclaredFields();
		for (Field f : declaredFields) {
			if (Modifier.isTransient(f.getModifiers()) || Modifier.isStatic(f.getModifiers())
					|| Modifier.isFinal(f.getModifiers()) || !f.isAnnotationPresent(Inject.class)) {
				continue;
			}
			setField(obj, f);
		}
	}

	private static void setField(Object obj, Field f) {
		try {
			if (!f.isAccessible()) {
				f.setAccessible(true);
			}
			if (f.get(obj) != null) {
				return;
			}
			Inject annotation = f.getAnnotation(Inject.class);
			if (annotation == null) {
				return;
			}

			Object value;
			if (!Strings.isEmpty(annotation.name())) {
				value = name2BeanCaches.get(annotation.name());
				if (value == null) {
					value = clazz2BeanCaches.get(f.getType());
				}
			} else {
				value = clazz2BeanCaches.get(f.getType());
			}
			if (value == null) {
				throw new RuntimeException("inject fail>>>" + obj.getClass().getName() + "." + f.getName());
			}
			f.set(obj, value);
		} catch (Exception e) {
			log.error("", e);
		}
	}

}
